/*
 * jQuery Rich Text Editor Plugin
 * version: 1.0.0 (July 1, 2011)
 *
 * Copyright (c) Takashi Yoshizawa
 *
 * http://yoshizawa.co.uk/
 *
 * Dual licensed under the MIT and GPL licenses:
 *	 http://www.opensource.org/licenses/mit-license.php
 *	 http://www.gnu.org/licenses/gpl.html
 */


 
if('undefined' === typeof Yoshizawa)
{
	Yoshizawa = {};
};



//*******************************************************



Yoshizawa.Editor = function(element, option)
{
	var buttons = [
		[
			'h1',
			'h2',
			'h3',
			'h4',
			'h5',
		],[
			'bold',
			'italic',
			'underline'
		],[
			'large',
			'medium',
			'small',
		],[
			'superscript',
			'subscript'
		],[
			'forecolor',
			'backcolor'
		],[
			'left',
			'center',
			'right'
		],[
			'p',
			'pre',
			'normal'
		],[
			'indent',
			'outdent',
			'orderedlist',
			'unorderedlist',
		],[
			'link',
			'unlink',
		],[
			'image',
			'table',
			'hr',
		],[
			'now'
		]
	];

	jQuery.data(element, Yoshizawa.Editor.DATA, this);

	this.option = option || {};
	this.textarea = $(element);
	this.div = $('<div class="yoshizawa-editor"></div>');
	this.textarea.before(this.div);

	this.iframe = document.createElement('iframe');
	this.iframe.frameBorder = 0;
	this.iframe.frameMargin= 0 ;
	this.iframe.framePadding = 5;
	this.iframe.height = this.textarea.height();

	$(this.iframe).appendTo(this.div);

	this.win = this.iframe.contentWindow;
	this.doc = this.win.document;
	this.utils = this.getUtils();
	
	this.initDoc();
	this.initToolbar(buttons);
	this.initEvents();	

	this.disableStyleWithCSS();
}



//*******************************************************



Yoshizawa.Editor.DATA = 'YoshizawaEditor';

Yoshizawa.Editor.CSS = {
	table: 'list',
};

Yoshizawa.Editor.KEY = {
	TAB: 9
};

Yoshizawa.Editor.Lang = 
{
	h1: 'Heading 1',
	h2: 'Heading 2',
	h3: 'Heading 3',
	h4: 'Heading 4',
	h5: 'Heading 5',
	
	bold: 'Bold',
	italic: 'Italic',
	underline: 'Underline',
	
	superscript: 'Superscript',
	subscript: 'Subscript',
	
	large: 'Font Size Large',
	medium: 'Font Size Medium',
	small: 'Font Size Small',
	forecolor: 'Fore Color',
	backcolor: 'Background Color',

	left: 'Align Left',
	center: 'Align Center',
	right: 'Align Right',
	
	p: 'Paragraph',
	pre: 'Preformatted',
	normal: 'Normal',
	
	indent: 'Indent',
	outdent: 'Outdent',
	orderedlist: 'Ordered List',
	unorderedlist: 'Unordered List',
	
	link: 'Link',
	unlink: 'Unlink',
	
	image: 'Image',
	table: 'Table',
	hr: 'Horizontal Rule',
	
	now: 'Now Date',
};



Yoshizawa.Editor.Utils = {};



Yoshizawa.Editor.Utils.Mozilla = {
	//-----------------------------------------------------
	//

	disableStyleWithCSS: function(editor)
	{
		try
		{
			editor.execCommand('styleWithCSS', 0, false);
		} catch (e) {
				try
			{
	  				editor.execCommand('useCSS', 0, true);
			} catch (e) {
				try
				{
					editor.execCommand('styleWithCSS', false, false);
				} catch (e) {
				}
			}
		}
	},
	
	//-----------------------------------------------------
	//

	insertHTML: function(editor, html)
	{
		editor.exec('insertHTML', html);
	},
	
	//-----------------------------------------------------
	//

	wrapHTML: function(editor, html)
	{
		var range = this.getRange(editor);
		var h = $(html);
		range.surroundContents(h.get(0));
	},
	
	//-----------------------------------------------------
	//
	
	removeFormat: function(editor)
	{
		var id = new Date().valueOf();
		var s = '<div id="' + id + '"></div>';
		this.wrapHTML(s);
		
		var selector = 'div#' + id;
		var div = $(selector, editor.doc);
		var text = div.text();
		
		var range = this.getRange(editor);
		range.surroundContents(text);
	},
	
	//-----------------------------------------------------
	//

	getRange: function(editor)
	{
		return this.getSelection(editor).getRangeAt(0);
	},
	
	//-----------------------------------------------------
	//

	getSelection: function(editor)
	{
		return editor.win.getSelection();
	}
}



//*******************************************************



Yoshizawa.Editor.Utils.InternetExplorer = {
	//-----------------------------------------------------
	//

	disableStyleWithCSS: function(editor)
	{
	},
	
	//-----------------------------------------------------
	//

	insertHTML: function(editor, html)
	{
		editor.win.focus();
		var range = this.getRange(editor);
		range.pasteHTML(html);
	},
	
	//-----------------------------------------------------
	//

	wrapHTML: function(editor, html)
	{
		editor.win.focus();
		var container = $('<div/>');
		var h = $(html).appendTo(container);
		var range = this.getRange(editor);

		var s1 = range.htmlText;
		h.html(s1);
		
		var s2 = container.html();
		range.pasteHTML(s2);
	},

	//-----------------------------------------------------
	//
	
	removeFormat: function(editor)
	{
		var id = new Date().valueOf();
		var s = '<div id="' + id + '"></div>';
		this.wrapHTML(s);
		
		var selector = 'div#' + id;
		var div = $(selector, editor.doc);
		var text = div.text();
		
		var range = this.getRange(editor);
		range.pasteHTML(text);
	},
	
	//-----------------------------------------------------
	//

	getRange: function(editor)
	{
	 	return this.getSelection(editor).createRange();
	},
	
	//-----------------------------------------------------
	//

	getSelection: function(editor)
	{
		return editor.doc.selection;
	}
};



//*******************************************************



Yoshizawa.Editor.prototype = {

	//-----------------------------------------------------
	//

	linkDialog: function(url)
	{
		return prompt('Enter URL', url);
	},
	
	//-----------------------------------------------------
	//

	imageDialog: function(url)
	{
		if('' == url || undefined == url)
		{
			url = 'http://';
		}
		
		return prompt('Enter URL', url);
	},
	
	//-----------------------------------------------------
	//
	
	colorDialog: function(color)
	{
		return prompt('Enter Color Code (ex. #00f)', color);
	},
	
	//-----------------------------------------------------
	//

	tableDialog: function()
	{
		var setting = {
			columnCount: 4,
			rowCount: 3,
		};
		
		return setting;
	},
	
	
	//-----------------------------------------------------
	//

	getUtils: function()
	{
	
		if($.browser.msie)
		{
			return Yoshizawa.Editor.Utils.InternetExplorer;
		}

		return Yoshizawa.Editor.Utils.Mozilla;
	},
	
	//-----------------------------------------------------
	//

	disableStyleWithCSS: function()
	{
		this.utils.disableStyleWithCSS(this);
	},
	
	//-----------------------------------------------------
	//
	
	insertHTML: function(html)
	{
		this.utils.insertHTML(this, html);
	},
	
	//-----------------------------------------------------
	//

	wrapHTML: function(html)
	{
		return this.utils.wrapHTML(this, html);
	},
	
	//-----------------------------------------------------
	//

	removeFormat: function()
	{
		this.warpHTML();
	},
	
	//-----------------------------------------------------
	//
	
	getRange: function()
	{
		return this.utils.getRange(this);
	},

	//-----------------------------------------------------
	//
	
 	getSelection: function()
	{
		return this.utils.getSelection(this);
	},

	//-----------------------------------------------------
	//
	
	initEvents: function()
	{
		var self = this;
		
		this.textarea.blur(function(){
			self.readContent();	
		});

		$(this.win).blur(function(){
			self.writeContent();
		});

		$(this.win).keydown(function(evt){
			return self.hookKeyEvents(evt);
		});
		
		$(this.doc).bind('keydown mousedown', function(evt){
			self.updateButtons();
		});
	},
	
	//-----------------------------------------------------
	//

	hookKeyEvents: function(evt)
	{
		switch(evt.keyCode)
		{
			case Yoshizawa.Editor.KEY.TAB:
				if(false == evt.shiftKey)
				{
					this.indent();
				} else {
					this.outdent();
				}

				evt.keyCode = 0;
				return false;
		}
	},
	
	//-----------------------------------------------------
	//

	initDoc: function()
	{
		var content = this.readContent();
		var markup = '<html><head>';
		
		if(this.option.baseUrl)
		{
			markup += '<base href="' + this.option.baseUrl + '" />';
		}
		
		if(this.option.stylesheetUrl)
		{
			markup += '<link rel="stylesheet" type="text/css" href="' + this.option.stylesheetUrl + '" />';
		}
		
		markup += '</head><body style="margin: 0; padding: 0;">' + content + '</body></html>';
		
		this.doc.open();
		this.doc.write(markup);
		this.doc.close();
		this.doc.designMode = 'on';
		this.doc.contentEditable = 'true';
		this.win.focus();
	},
	
	//-----------------------------------------------------
	//

	writeContent: function()
	{
		this.textarea.val(this.doc.body.innerHTML);
	},

	//-----------------------------------------------------
	//

	readContent: function()
	{
		var s = this.textarea.val();

		if('' == s)
		{
			s = '<br />';
		}
		
		return s;
	},
	
	//-----------------------------------------------------
	//

	exec: function(command, option)
	{
		this.win.focus();
		this.doc.execCommand(command, false, option);
		this.writeContent();
		this.win.focus();
	},

	//-----------------------------------------------------
	//

	initToolbar: function(buttons)
	{
		this.toolbar = $('<div class="toolbar"></div>').prependTo(this.div);
		
		for(var groupIndex in buttons)
		{
			var tr = $('<table class="toolbar-group"><tbody><tr></tr></tbody></table>').appendTo(this.toolbar).find('tr'); 
			var group = buttons[groupIndex];
			
			for(var buttonIndex in group)
			{
				var name = group[buttonIndex];
				var title = Yoshizawa.Editor.Lang[name] || name;
				var markup = '<td class="toolbar-button toolbar-button-' + name + '"><a href="#" onclick="javascript:void(0);" title="' + title + '"></a></td>';
				var data = {
					self: this,
					name: name
				};
				
				$(markup).appendTo(tr).click(data, function(evt){
					var a = $(evt.target);
					var b = a.hasClass('disabled');
					if(b)
					{
						return false;
					}
					
					var self = evt.data.self;
					var name = evt.data.name;
					var func = self[name];
					func.call(self);;
					return false;
				});
			}
			
			tr.append('<td class="toolbar-separator"></td');
		}
	},

	//-----------------------------------------------------
	//

	updateButtons: function()
	{
		console.log('update:' + new Date().valueOf());
		
		var name = 'bold';
		var selector = '.toolbar-button-' + name + ', .toolbar-button-' + name + ' a';
		var button = this.toolbar.find(selector); // TD and A
		var b = this.doc.queryCommandEnabled(name);

		if(b)
		{
			button.removeClass('disabled').removeAttr('disabled', 'disabled');
		} else {
			button.addClass('disabled').attr('disabled', 'disabled');
		}
	},
	
	//-----------------------------------------------------
	//

	formatBlockTag: function(tag)
	{
		if($.browser.msie || $.browser.safari)
		{
			return '<' + tag + '>';
		}
		
		return tag;
	},
	
	//-----------------------------------------------------
	//

	h1: function()
	{
		this.exec('formatblock', this.formatBlockTag('h1'));
	},

	//-----------------------------------------------------
	//

	h2: function()
	{
		this.exec('formatblock',  this.formatBlockTag('h2'));
	},

	//-----------------------------------------------------
	//

	h3: function()
	{
		this.exec('formatblock',  this.formatBlockTag('h3'));
	},

	//-----------------------------------------------------
	//

	h4: function()
	{
		this.exec('formatblock',  this.formatBlockTag('h4'));
	},

	//-----------------------------------------------------
	//

	h5: function()
	{
		this.exec('formatblock',  this.formatBlockTag('h5'));
	},
	
	//-----------------------------------------------------
	//

	bold : function()
	{
		this.exec('bold');
	},

	//-----------------------------------------------------
	//

	italic : function()
	{
		this.exec('italic');
	},

	//-----------------------------------------------------
	//

	underline : function()
	{
		this.exec('underline');
	},
	
	//-----------------------------------------------------
	//

	strike: function()
	{
		this.exec('strikeThrough');
	},
	
	//-----------------------------------------------------
	//

	underline : function()
	{
		this.exec('underline');
	},

	//-----------------------------------------------------
	//

	superscript : function()
	{
		this.exec('superscript');
	},

	//-----------------------------------------------------
	//

	subscript : function()
	{
		this.exec('subscript');
	},

	//-----------------------------------------------------
	//

	large : function()
	{
		this.exec('fontsize', 5);
	},

	//-----------------------------------------------------
	//

	medium : function()
	{
		this.exec('fontsize', 3);
	},

	//-----------------------------------------------------
	//

	small : function()
	{
		this.exec('fontsize', 1);
	},

	//-----------------------------------------------------
	//

	forecolor : function()
	{
		var color = this.colorDialog();
		this.exec('forecolor', color);
	},

	//-----------------------------------------------------
	//

	backcolor : function()
	{
		var color = this.colorDialog();
		this.exec('backColor', color);
	},

	//-----------------------------------------------------
	//

	left : function()
	{
		this.exec('justifyLeft');
	},

	//-----------------------------------------------------
	//

	center : function()
	{
		this.exec('justifyCenter');
	},

	//-----------------------------------------------------
	//

	right : function()
	{
		this.exec('justifyRight');
	},

	//-----------------------------------------------------
	//

	p : function()
	{
		this.exec('formatblock', this.formatBlockTag('p'));
	},

	//-----------------------------------------------------
	//

	pre : function()
	{
		this.exec('formatblock', this.formatBlockTag('pre'));
	},

	//-----------------------------------------------------
	//

	quote: function()
	{
		this.wrapHTML('q');
	},
	
	//-----------------------------------------------------
	//

	normal : function()
	{
		this.exec('removeformat');
	},

	//-----------------------------------------------------
	//

	indent : function()
	{
		this.exec('indent');
	},

	//-----------------------------------------------------
	//

	outdent : function()
	{
		this.exec('outdent');
	},

	//-----------------------------------------------------
	//

	orderedlist : function()
	{
		this.exec('insertOrderedList');
	},

	//-----------------------------------------------------
	//

	unorderedlist : function()
	{
		this.exec('insertUnorderedList');
	},

	//-----------------------------------------------------
	//

	link : function()
	{
		var url = this.linkDialog();

		if(false === url)
		{
			return;
		}

		this.exec('createLink', url);
	},


	//-----------------------------------------------------
	//

	unlink : function()
	{
		this.exec('unlink');
	},


	//-----------------------------------------------------
	//

	image : function()
	{
		var url = this.imageDialog();
		
		if(false === url)
		{
			return;
		}
		
		this.exec('insertImage', url);
	},

	//-----------------------------------------------------
	//

	table : function()
	{
		var setting = this.tableDialog();
		var s = '<table border="1" ' + Yoshizawa.Editor.CSS.table + '">';
		
		setting.columnCount = setting.columnCount || 2;
		setting.rowCount = setting.rowCount || 2;
		
		s += '<thead><tr>';
		
		for(var j=0; j<setting.columnCount; j++)
		{
			s += '<td>&nbsp;</td>';
		}
		
		s += '</tr></thead><tbody>';
		
		for(var i=0; i<setting.rowCount; i++)
		{
			s += '<tr>';
			
			for(var j=0; j<setting.columnCount; j++)
			{
				s += '<td>&nbsp;</td>';
			}
			
			s += '</tr>';
		}
		
		s += '</tbody></table>';

		this.insertHTML(s);
	},

	//-----------------------------------------------------
	//

	insertRowBefore : function()
	{
		throw new Error('Not Implemented');
	},

	//-----------------------------------------------------
	//

	insertRowAfter : function()
	{
		throw new Error('Not Implemented');
	},

	//-----------------------------------------------------
	//

	deleteRow : function()
	{
		throw new Error('Not Implemented');
	},

	//-----------------------------------------------------
	//

	insertColumnBefore: function()
	{
		throw new Error('Not Implemented');
	},

	//-----------------------------------------------------
	//

	insertColumnAfter: function()
	{
		throw new Error('Not Implemented');
	},
	
	//-----------------------------------------------------
	//

	deleteColumn: function()
	{
		throw new Error('Not Implemented');
	},
	
	//-----------------------------------------------------
	//

	margeCells: function()
	{
		throw new Error('Not Implemented');
	},

	//-----------------------------------------------------
	//

	splitCells: function()
	{
		throw new Error('Not Implemented');
	},

	//-----------------------------------------------------
	//

	hr : function()
	{
		this.exec('insertHorizontalRule');
	},

	//-----------------------------------------------------
	//

	now: function()
	{
		var s = new Date().toLocaleDateString();
		
		this.insertHTML(s);
	},
};



//*******************************************************



(function($){
	$.fn.editor = function(option)
	{
		return $(this).each(function(){
			var element = this;
			var editor =  jQuery.data(element, Yoshizawa.Editor.DATA);
			
			if('undefined' === typeof editor)
			{
				editor = new Yoshizawa.Editor(element, option);
			}

			return true;
		});
	}
})(jQuery);
